use crate::parser::{
  ASTBinaryExpression, ASTBinaryOperatorKind, ASTBooleanExpression, ASTNumberExpression,
  ASTUnaryExpression, ASTUnaryOperatorKind, ASTVisitor, Ast,
};

#[derive(Debug, Clone, Copy)]
pub enum Value {
  Number(f64),
  Boolean(bool),
}

impl Value {
  pub fn expect_number(&self) -> f64 {
    match self {
      Value::Number(value) => *value,
      _ => panic!("Expected number value"),
    }
  }

  pub fn expect_boolean(&self) -> bool {
    match self {
      Value::Boolean(value) => *value,
      _ => panic!("Expected boolean value"),
    }
  }
}

pub struct ASTEvaluator {
  pub last_value: Option<Value>,
}

impl ASTEvaluator {
  pub fn new() -> Self {
    Self { last_value: None }
  }

  pub fn result(&self) -> Option<Value> {
    self.last_value
  }

  fn expect_last_value(&self) -> Value {
    *self
      .last_value
      .as_ref()
      .expect("Expected last value to be set")
  }

  pub fn print_result(&self, mut ast: Ast) -> Value {
    let mut eval = Self::new();
    ast.visit(&mut eval);

    eval.result().unwrap()
  }
}

impl ASTVisitor for ASTEvaluator {
  fn visit_number(&mut self, number_expr: &ASTNumberExpression) {
    self.last_value = Some(Value::Number(number_expr.number));
  }

  fn visit_binary_expression(&mut self, binary_expr: &ASTBinaryExpression) {
    self.visit_expression(&binary_expr.lhs);
    let lhs = self.expect_last_value().expect_number();
    self.visit_expression(&binary_expr.rhs);
    let rhs = self.expect_last_value().expect_number();

    let result = match binary_expr.operator.kind {
      ASTBinaryOperatorKind::Plus => lhs + rhs,
      ASTBinaryOperatorKind::Minus => lhs - rhs,
      ASTBinaryOperatorKind::Multiply => lhs * rhs,
      ASTBinaryOperatorKind::Divide => lhs / rhs,
    };

    self.last_value = Some(Value::Number(result));
  }

  fn visit_unary_expression(&mut self, unary_expr: &ASTUnaryExpression) {
    self.visit_expression(&unary_expr.operand);
    let operand = self.expect_last_value();
    self.last_value = match unary_expr.operator.kind {
      ASTUnaryOperatorKind::Minus => Some(Value::Number(-operand.expect_number())),
      ASTUnaryOperatorKind::BitwiseNot => Some(Value::Boolean(!operand.expect_boolean())),
    };
  }

  fn visit_boolean_expression(&mut self, boolean_expr: &ASTBooleanExpression) {
    self.last_value = Some(Value::Boolean(boolean_expr.value));
  }
}
